package Ms

import (
	"encoding/json"
	"fmt"
	"db2s/ETL"
	"db2s/global"
	"reflect"
	"runtime"
	"strconv"
	"strings"
	"time"
)

func (ms TableInfoMeta) IfTablePartitionSum(s global.TablesMetaInfoInput) (partitionData global.PartitionSumReturnResult, err error) {
	//var (
	//	event = "[sqlserverIfTablePartitionSum]"
	//	res   any
	//)
	//strSql := fmt.Sprintf("select spf.type_desc as partitionType,spf.fanout as sum from sys.indexes si INNER JOIN sys.partition_schemes sps on si.data_space_id = sps.data_space_id LEFT JOIN sys.partition_functions spf on sps.function_id = spf.function_id where si.object_id = OBJECT_ID('%v.%v')", s.TableInfo.Schema, s.TableInfo.Table)
	//if _, res, err = ExecSelectSql(s, strSql, dataDispos.Map); err != nil || res == nil {
	//	if err != nil {
	//		err = errors.New(fmt.Sprintf("%v %v", event, err))
	//	}
	//	return
	//}
	//if v1, ok := res.(map[string]any)["partitionType"]; ok {
	//	partitionData.Type = fmt.Sprintf("%v", v1)
	//}
	//if v1, ok := res.(map[string]any)["sum"]; ok {
	//	if len(fmt.Sprintf("%v", v1)) > 0 {
	//		if v2, err1 := strconv.ParseInt(fmt.Sprintf("%v", v1), 10, 64); err1 != nil {
	//			err = errors.New(fmt.Sprintf("%v table %v.%v partition sum conversion int failed. data is %v, error is %v", event, s.TableInfo.Schema, s.TableInfo.Table, res, err))
	//			return partitionData, err
	//		} else {
	//			partitionData.Sum = v2
	//		}
	//	}
	//}
	//if len(partitionData.Type) > 0 && partitionData.Sum > 0 {
	//	return partitionData, nil
	//}
	return
}

func (ms TableInfoMeta) TableDistributed(s global.TablesMetaInfoInput) (any, error) {
	var (
		event = "[TableDistributed]"
		tpc   []global.GetTableDistributed
	)
	pc, _, _, _ := runtime.Caller(1)
	callingFunc := runtime.FuncForPC(pc)
	callFile, callLine := callingFunc.FileLine(pc)
	callFunc := callingFunc.Name()
	callFuncInfo := fmt.Sprintf("Call functions %v Call File %v:%v", callFunc, callFile, callLine)
	vlog = fmt.Sprintf("(%d) %v %v Start checking whether table %v.%v exists.", s.LogSeq, callFuncInfo, event, s.TableInfo.Schema, s.TableInfo.Table)
	WLog.Debug(vlog)
	tpc = append(tpc, global.GetTableDistributed{
		Schema:           s.TableInfo.Schema,
		Table:            s.TableInfo.Table,
		DistributeMode:   "NORMAL",
		ShardName:        "single",
		BackendTableName: s.TableInfo.Table,
	})
	return tpc, nil
}

func (ms TableInfoMeta) anyToString(s any, t string) (r string) {
	if s == nil {
		return
	}
	switch fmt.Sprintf("%v", reflect.TypeOf(s)) {
	case "bool":
		return
	case "time.Time":
		switch strings.ToUpper(t) {
		case "DATE":
			utc := "2006-01-02"
			r = fmt.Sprintf("'%v'", s.(time.Time).UTC().Format(utc))
		case "TIME":
			utc := "15:04:05"
			r = fmt.Sprintf("'%v'", s.(time.Time).UTC().Format(utc))
		case "SMALLDATETIME", "DATETIME", "DATETIME2", "DATETIMEOFFSET":
			utc := "2006-01-02 15:04:05"
			r = fmt.Sprintf("'%v'", s.(time.Time).UTC().Format(utc))
		default:
			return fmt.Sprintf("%v", s)
		}
	}
	return
}
func (ms TableInfoMeta) partColumnActive(columnName []string, columnMeta []global.TableMeta) global.PartColumnMeta {
	var pc global.PartColumnMeta
	for _, v := range columnName {
		for _, p := range columnMeta {
			if strings.EqualFold(v, p.ColumnName) {
				pc.Type = append(pc.Type, p.ConvertColumnType.Type)
				pc.CharSet = append(pc.CharSet, p.Charset)
				pc.Collation = append(pc.Collation, p.CollationName)
			}
		}
	}
	return pc
}

type partitionBaseMeta struct {
	columnMeta       []global.TableMeta
	partitionMeta    []global.GetPartitionMeta
	tableDistributed []global.GetTableDistributed
}

func (ms TableInfoMeta) partitionBaseMetaActive(s global.TablesMetaInfoInput) (p partitionBaseMeta, err error) {
	var (
		event     = "[PartitionConfig]"
		f1        any
		allResult global.AllReturnResult
		tc        TableColumn
	)
	vlog = fmt.Sprintf("(%d) %v Start checking whether table %v.%v exists.", s.LogSeq, event, s.TableInfo.Schema, s.TableInfo.Table)
	WLog.Debug(vlog)
	//获取表的列信息
	if allResult, err = tc.All(global.GetColumnTypeInput{
		LogSeq: s.LogSeq,
		DB:     s.DB,
		TableInfo: global.TableInfo{
			Schema: s.TableInfo.Schema,
			Table:  s.TableInfo.Table,
		},
	}); err != nil || f1 == nil {
		return
	} else {
		p.columnMeta = allResult.Result
	}
	//获取表的分区信息
	if f1, err = ms.GetPartitionMeta(s); err != nil {
		return
	}
	if f1 != nil && len(f1.([]global.GetPartitionMeta)) > 0 {
		p.partitionMeta = f1.([]global.GetPartitionMeta)
	}
	//获取表分布信息
	if f1, err = ms.TableDistributed(s); err != nil || f1 == nil {
		return
	}
	if f1 != nil && len(f1.([]global.GetTableDistributed)) > 0 {
		p.tableDistributed = f1.([]global.GetTableDistributed)
	}
	return
}

// FirstSubPartNameMarge 合并first Sub partition name
func (ms TableInfoMeta) FirstSubPartNameMarge(s global.TablesMetaInfoInput) (q any, err error) {
	var (
		partitionShardMap   = make(map[string]global.BackendTable)
		PartMeth            string
		PartColumn          global.ManyLevelPartColumn
		partitionNameGarth  []string
		FirstNameMapSubName = make(map[string][]string)
		p                   partitionBaseMeta
	)
	if p, err = ms.partitionBaseMetaActive(s); err != nil {
		return
	}
	for _, j := range p.tableDistributed {
		var key = j.FirstPartitionName
		if len(j.SubPartitionName) > 0 {
			key = fmt.Sprintf("%v#%v", key, j.SubPartitionName)
		}
		if strings.EqualFold(j.DistributeMode, "NORMAL") {
			key = "single"
		}
		partitionShardMap[key] = global.BackendTable{
			FirstPartitionName: j.FirstPartitionName,
			SubPartitionName:   j.SubPartitionName,
			ShardName:          j.ShardName,
			BackendTableName:   j.BackendTableName,
		}
	}
	//normal表
	if len(p.partitionMeta) == 0 {
		return global.FirstSubPartNameMargeData{
			PartMeth:              "single",
			FirstNameMapSubName:   FirstNameMapSubName,
			PartColumn:            PartColumn,
			PartitionNameGarth:    []string{"single"},
			PartitionBackendShard: partitionShardMap,
		}, nil
	}
	for _, v := range p.partitionMeta {
		//分区方法
		if len(PartMeth) == 0 {
			//分区方法合并
			PartMeth = v.FirstPartMeth
			if len(v.SubPartMeth) > 0 && v.SubPartMeth != "NULL" && v.SubPartMeth != "''" {
				PartMeth = fmt.Sprintf("%v_%v", PartMeth, v.SubPartMeth)
			}
			//分区列信息
			PartColumn.FirstPar = ms.partColumnActive(v.FirstPartColumn, p.columnMeta)
			if len(v.SubPartColumn) > 0 {
				PartColumn.SubPar = ms.partColumnActive(v.SubPartColumn, p.columnMeta)
			}
		}
		//分区名合并
		var key = v.FirstPartName
		if len(v.SubPartName) > 0 && v.SubPartName != "NULL" && v.SubPartName != "''" {
			key = fmt.Sprintf("%v#%v", key, v.SubPartName)
		}
		partitionNameGarth = append(partitionNameGarth, key)
		if _, ok := FirstNameMapSubName[v.FirstPartName]; !ok {
			if len(v.SubPartName) > 0 {
				FirstNameMapSubName[v.FirstPartName] = []string{v.SubPartName}
			} else {
				FirstNameMapSubName[v.FirstPartName] = []string{}
			}
		} else {
			if len(v.SubPartName) > 0 && v.SubPartName != "NULL" && v.SubPartName != "''" {
				FirstNameMapSubName[v.FirstPartName] = append(FirstNameMapSubName[v.FirstPartName], v.SubPartName)
			}
		}
	}
	return global.FirstSubPartNameMargeData{
		PartMeth:              PartMeth,
		FirstNameMapSubName:   FirstNameMapSubName,
		PartColumn:            PartColumn,
		PartitionNameGarth:    partitionNameGarth,
		PartitionBackendShard: partitionShardMap,
	}, nil
}

//disPartRules 处理每个分区的分区规则
/*
	处理如下：
	1：拆分分区规则中是否用到分区函数
	2：合并分区规则中多个值现象（主要是list集合）
	传参：每个分区数据
	返值：分区规则
*/
func (ms TableInfoMeta) disPartRules(meth string, s any) (r string) {
	var (
		direction bool
	)
	v := s.(map[string]any)
	switch meth {
	case "RANGE":
		if vv, ok := v["direction"]; ok {
			switch fmt.Sprintf("%v", reflect.TypeOf(vv)) {
			case "bool":
				direction = vv.(bool)
			}
		}
		switch direction {
		case false: //表示边界值不包含在右侧分区中，而是包含在左侧分区中
			if vv, ok := v["leftValue"]; ok {
				if vv != nil {
					r = ms.anyToString(vv, fmt.Sprintf("%v", v["firstPartColumnType"]))
				}
			}
			//rightValue
		case true: //表示边界值包含在右侧分区中
			if vv, ok := v["rightValue"]; ok {
				if vv == nil {
					r = "MAXVALUE"
				} else {
					r = ms.anyToString(vv, fmt.Sprintf("%v", v["firstPartColumnType"]))
				}
			}
		}

	case "LIST":
		l := fmt.Sprintf("%v", s)
		if len(l) == 0 {
			r = fmt.Sprintf("%v", s)
			return
		}
		if strings.Contains(l, ",") {
			var p []string
			for _, v := range strings.Split(l, ",") {
				p = append(p, strings.TrimSpace(v))
			}
			r = strings.Join(p, ",")
		}
	default:
		r = fmt.Sprintf("%v", s)
	}
	return
}

func (ms TableInfoMeta) GetPartitionMeta(s global.TablesMetaInfoInput) (any, error) {
	var (
		event       = "[GetPartitionMeta]"
		execTimeout int64
		f1          any
		tpc         []global.GetPartitionMeta
		err         error
		tableName   = fmt.Sprintf("%v.%v", s.TableInfo.Schema, s.TableInfo.Table)
	)
	pc, _, _, _ := runtime.Caller(1)
	callingFunc := runtime.FuncForPC(pc)
	callFile, callLine := callingFunc.FileLine(pc)
	callFunc := callingFunc.Name()
	callFuncInfo := fmt.Sprintf("Call functions %v Call File %v:%v", callFunc, callFile, callLine)
	vlog = fmt.Sprintf("(%d) %v %v Start checking whether table %v.%v exists.", s.LogSeq, callFuncInfo, event, s.TableInfo.Schema, s.TableInfo.Table)
	WLog.Debug(vlog)
	strSql := fmt.Sprintf("SELECT OBJECT_SCHEMA_NAME(i.object_id) as [schema], OBJECT_NAME(i.object_id) AS [table],(F.name + '(' + c.name + ')_' + CONVERT(varchar(64), p.partition_number)) AS [firstPartName],CONVERT(varchar(64), p.partition_number) as [firstPartSeq],f.type_desc as [firstPartMeth], c.name AS [firstPartColumn],ic.data_type AS [firstPartColumnType],f.boundary_value_on_right AS direction, rv2.value as leftValue,rv.value as rightValue FROM sys.tables AS t JOIN sys.indexes AS i ON t.object_id = i.object_id JOIN sys.partitions AS p  ON i.object_id = p.object_id AND i.index_id = p.index_id JOIN  sys.partition_schemes AS s ON i.data_space_id = s.data_space_id JOIN sys.partition_functions AS f ON s.function_id = f.function_id  LEFT JOIN sys.partition_range_values AS r ON f.function_id = r.function_id and r.boundary_id = p.partition_number LEFT JOIN sys.partition_range_values AS rv  ON f.function_id = rv.function_id  AND p.partition_number = rv.boundary_id LEFT JOIN sys.partition_range_values AS rv2 ON f.function_id = rv2.function_id AND p.partition_number - 1= rv2.boundary_id INNER JOIN sys.index_columns sic on i.OBJECT_ID = sic.object_id INNER JOIN sys.columns as c ON sic.column_id = c.column_id and c.OBJECT_ID = i.OBJECT_ID INNER JOIN INFORMATION_SCHEMA.columns ic ON c.Name = ic.column_name WHERE c.OBJECT_ID = OBJECT_ID('%v') and ic.table_schema= '%v' and ic.TABLE_NAME = '%v'  and i.type <= 1 and sic.partition_ordinal >0 and p.object_Id in (select object_Id from sys.partitions where OBJECT_ID = OBJECT_ID('%v') group by object_id having max(partition_number) >1 ) ORDER BY p.partition_number", tableName, s.TableInfo.Schema, s.TableInfo.Table, tableName)
	active := ETL.InstanceActiveS{DBType: DBType, DB: s.DB["single"], SqlMode: true}
	if execTimeout, f1, err = active.SqlQuery(strSql, ETL.SMap); err != nil {
		vlog = fmt.Sprintf("(%d) %v %v The execution of a sql statement failed. sql is {%v},error is {%v}", s.LogSeq, callFuncInfo, event, strSql, err)
		WLog.Warn(vlog)
		return nil, err
	}
	vlog = fmt.Sprintf("(%d) %v %v A sql statement was executed successfully. sql is {%v}, exec timeout is {%vms}", s.LogSeq, callFuncInfo, event, strSql, execTimeout)
	WLog.Debug(vlog)
	if f1 == nil {
		return tpc, nil
	}
	var columnFunc = map[string]string{"firstPartColumn": "firstPartColumnFunc", "subPartColumn": "subPartColumnFunc"}
	for _, v := range f1.([]map[string]interface{}) {
		u := v
		//处理partition meth
		if w, ok := v["firstPartMeth"]; ok {
			meth := strings.ToUpper(fmt.Sprintf("%v", w))
			if strings.Contains(meth, "COLUMNS") {
				meth = strings.TrimSpace(strings.ReplaceAll(meth, "COLUMNS", ""))
			}
			u["firstPartMeth"] = meth
		}
		//处理partition meth
		if z := ms.disPartRules(u["firstPartMeth"].(string), v); len(z) > 0 {
			u["partitionRules"] = z
		} else {
			continue
		}
		//处理partition name

		//处理first sub partition column and first sub partition func
		for _, z := range []string{"firstPartColumn", "subPartColumn"} {
			if w, ok := v[z]; ok {
				meth := strings.ToUpper(fmt.Sprintf("%v", w))
				var y, t []string
				for _, x := range strings.Split(strings.ReplaceAll(meth, "`", ""), ",") {
					if strings.Contains(x, "(") && strings.Contains(x, ")") {
						var funcName, columnName string
						var left, right int
						if left = strings.Index(x, "("); left != -1 {
							funcName = x[:left]
						}
						if right = strings.Index(x, ")"); right != -1 {
							columnName = x[left+1 : right]
						}
						t = append(t, strings.TrimSpace(funcName))
						y = append(y, strings.TrimSpace(columnName))
					} else {
						y = append(y, strings.TrimSpace(x))
					}

				}
				u[z] = y
				u[columnFunc[z]] = t
			}
		}
		v1 := global.GetPartitionMeta{}
		// 将 map 转换为 JSON 字节流
		var jsonData []byte
		jsonData, err = json.Marshal(v)
		if err != nil {
			vlog = fmt.Sprintf("(%d) %v %s map[string]interfacle strconv json fail. map data is {%v} err info is {%v}", s.LogSeq, callFuncInfo, event, v, err)
			WLog.Error(vlog)
			return nil, err
		}
		// 将 JSON 字节流反序列化为结构体
		err = json.Unmarshal(jsonData, &v1)
		if err != nil {
			vlog = fmt.Sprintf("(%d) %v %s json strconv struct fail. json data is {%v} err info is {%v}", s.LogSeq, callFuncInfo, event, jsonData, err)
			WLog.Error(vlog)
			return nil, err
		}
		tpc = append(tpc, v1)
	}
	return tpc, nil
}
func (ms TableInfoMeta) PartitionConfig(s global.TablesMetaInfoInput) (any, error) {
	var (
		event               = "[PartitionConfig]"
		f1                  any
		tc                  TableColumn
		columnMeta          []global.TableMeta
		PartitionMeta       []global.GetPartitionMeta
		TableDistributed    []global.GetTableDistributed
		tpc                 []global.TablePartitionConfig
		firstPartitionMarge = make(map[string]int)
		firstPartitionGarth []global.SubPartitionMeta
		subPartitionMarge   = make(map[string][]global.SubPartitionMeta)
		err                 error
	)
	pc, _, _, _ := runtime.Caller(1)
	callingFunc := runtime.FuncForPC(pc)
	callFile, callLine := callingFunc.FileLine(pc)
	callFunc := callingFunc.Name()
	callFuncInfo := fmt.Sprintf("Call functions %v Call File %v:%v", callFunc, callFile, callLine)
	vlog = fmt.Sprintf("(%d) %v %v Start checking whether table %v.%v exists.", s.LogSeq, callFuncInfo, event, s.TableInfo.Schema, s.TableInfo.Table)
	WLog.Debug(vlog)
	//获取表的列信息
	if f1, err = tc.All(global.GetColumnTypeInput{
		LogSeq: s.LogSeq,
		DB:     s.DB,
		TableInfo: global.TableInfo{
			Schema: s.TableInfo.Schema,
			Table:  s.TableInfo.Table,
		},
	}); err != nil || f1 == nil {
		return nil, err
	}
	columnMeta = f1.([]global.TableMeta)
	//获取表的分区信息
	if f1, err = ms.GetPartitionMeta(s); err != nil || f1 == nil {
		return nil, err
	}
	PartitionMeta = f1.([]global.GetPartitionMeta)
	//获取表分布信息
	if f1, err = ms.TableDistributed(s); err != nil || f1 == nil {
		return nil, err
	}
	TableDistributed = f1.([]global.GetTableDistributed)
	var (
		getColumnType = func(x []string, o []global.TableMeta) (z []string) {
			for _, v := range o {
				for _, y := range x {
					if strings.EqualFold(v.ColumnName, y) {
						z = append(z, v.ColumnType)
					}
				}
			}
			return
		}
	)
	if len(PartitionMeta) == 0 {
		tpc = append(tpc, global.TablePartitionConfig{
			Schema:      s.TableInfo.Schema,
			Table:       s.TableInfo.Table,
			TableObject: "single",
			FirstPartition: global.SubPartitionMeta{
				Name: "single",
			},
			BackendTableName: s.TableInfo.Table,
			ShardName:        TableDistributed[0].ShardName,
		})
		return tpc, nil
	}
	for _, v := range PartitionMeta {
		if _, ok := firstPartitionMarge[v.FirstPartName]; !ok {
			firstPartitionGarth = append(firstPartitionGarth, global.SubPartitionMeta{
				Sum:        strconv.Itoa(len(PartitionMeta)),
				Func:       v.FirstPartColumnFunc,
				Name:       v.FirstPartName,
				Meth:       v.FirstPartMeth,
				ColumnName: v.FirstPartColumn,
				ColumnType: getColumnType(v.FirstPartColumn, columnMeta),
				Rules:      v.PartRul,
				Seq:        v.FirstPartSeq,
			})
			subPartitionMarge[v.FirstPartName] = []global.SubPartitionMeta{{
				Func:       v.SubPartColumnFunc,
				Name:       v.SubPartName,
				Meth:       v.SubPartMeth,
				ColumnName: v.SubPartColumn,
				ColumnType: getColumnType(v.SubPartColumn, columnMeta),
				Rules:      v.PartRul,
				Seq:        v.SubPartSeq,
			}}
			firstPartitionMarge[v.FirstPartName]++
		} else {
			subPartitionMarge[v.FirstPartName] = append(subPartitionMarge[v.FirstPartName], global.SubPartitionMeta{
				Func:       v.SubPartColumnFunc,
				Name:       v.SubPartName,
				Meth:       v.SubPartMeth,
				ColumnName: v.SubPartColumn,
				ColumnType: getColumnType(v.SubPartColumn, columnMeta),
				Rules:      v.PartRul,
				Seq:        v.SubPartSeq,
			})
		}
	}
	for _, v := range firstPartitionGarth {
		var subPartGarth []global.SubPartitionMeta
		var subPartMeth string
		if w, ok := subPartitionMarge[v.Name]; ok {
			subPartGarth = w
		}
		if len(subPartGarth) > 0 {
			subPartMeth = subPartGarth[0].Meth
		}
		tpc = append(tpc, global.TablePartitionConfig{
			Schema:           s.TableInfo.Schema,
			Table:            s.TableInfo.Table,
			FirstPartition:   v,
			SubPartMeth:      subPartMeth,
			SubPartMeta:      subPartGarth,
			ShardName:        "single",
			TableObject:      "partition",
			BackendTableName: s.TableInfo.Table,
		})
	}
	return tpc, nil
}

// ----------new partition
func (ms TableInfoMeta) GetPartMeta(s global.TablesMetaInfoInput) (res global.PartMetaResultS, err error) {
	return
}
